package com.itheima.exception;

import com.itheima.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.List;
import java.util.StringJoiner;

/**
 * @version 1.0
 * @description 项目的全局异常处理器
 * 异常处理的分类:
 *   1、已知异常，业务异常，处理业务逻辑时，校验判断出不符合后续业务要求的抛出的异常，作用：终止后续代码的执行
 *     给予用户友好提示，区分系统业务异常与系统异常，终止后续代码的执行
 *     自定义异常类，继承RuntimeException, 参考: BizException
 *   2、可预知的异常：Timeout 可预知
 *     给予用户友好提示、记录异常信息，通知运维人员
 *   3、未知异常：完全无法预测的
 *    给予用户友好提示，记录异常信息，通知运维人员与开发人员
 * @package com.itheima.exception
 */
@RestControllerAdvice //全局异常处理器标识，如果controller出异常，就来找这个类，哪个方法呢，@ExceptionHandler注解的方法，默认方法的形参类型就是异常的类型
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 有多个异常类型时，按最小的优先级来处理异常，如果找不到子类异常处理的方法，则按上级父类异常，逐层处理
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result handlerException(Exception e){
        log.error("发生未知异常!", e);
        return Result.error("系统异常，请联系管理员!");
    }

    /**
     * 业务异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(BizException.class)
    public Result handlerBizException(BizException e){
        return Result.error(e.getMessage());
    }

    /**
     * 实体类校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handlerMethodArgumentNotValidException(MethodArgumentNotValidException e){
        // 校验结果
        BindingResult bindingResult = e.getBindingResult();
        // 校验结果中的所有未通过的提示信息
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        StringJoiner s = new StringJoiner(",");
        for (ObjectError allError : allErrors) {
            s.add(allError.getDefaultMessage());
        }
        return Result.success(s.toString());
    }

    /**
     * 方法中的单个参数校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(HandlerMethodValidationException.class)
    public Result handlerHandlerMethodValidationException(HandlerMethodValidationException e){
        log.error("参数校验异常!", e);
        // 获取所有校验未通过信息
        List<? extends MessageSourceResolvable> allErrors = e.getAllErrors();
        // 拼接所有错误信息, 字符串拼接器，每拼接一个字符串后会自动添加一个逗号
        StringJoiner s = new StringJoiner(",");
        // 遍历每个校验失败的信息, 哪个参数哪属性校验没通过
        for (MessageSourceResolvable allError : allErrors) {
            // 拼接字符串, getDefaultMessage校验未通过时的提示信息，@NotBlank(message属性值)
            s.add(allError.getDefaultMessage());
        }
        // 友好提示，校验未通过的信息
        return Result.error(s.toString());
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result handlerDuplicateKeyException(DuplicateKeyException e){
        log.error("发生未知异常!", e);
        // 获取异常提示信息,
        // e.getCause()的原因是：抛出异常是由数据库层面抛出的，经由spring框架包装成DuplicateKeyException，
        // 要获取真正的异常信息就得先获取根本的异常
        String message = e.getCause().getMessage();
        String[] split = message.split("\\s+");

        return Result.error(split[2] + " 已存在了!");
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result handlerMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e){
        log.error("参数类型转换异常!", e);
        return Result.error( "参数" + e.getParameter().getParameterName() + " 类型不正确!");
    }


}
